import 'dart:math';
import 'dart:typed_data';
import 'package:decimal/decimal.dart';

/// @author: slc
/// @date: 2024/03/11
/// 随机工具
class RandomUtil {
  static const String baseNumber = "0123456789";
  static const String baseChar = "abcdefghijklmnopqrstuvwxyz";
  static const String baseCharNumberLower = "abcdefghijklmnopqrstuvwxyz0123456789";
  static const String baseCharNumber = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  static Random getRandom() => Random();

  static Random getSecureRandom() => Random.secure();

  static bool randomBoolean() => getRandom().nextBool();

  /// 生成指定长度的随机字节数组
  static Uint8List randomBytes(int length) {
    final random = getSecureRandom();
    return Uint8List.fromList(List.generate(length, (_) => random.nextInt(256)));
  }

  static int randomInt(int limitExclude) => getRandom().nextInt(limitExclude);

  static int randomIntRange(int minInclude, int maxExclude) {
    return minInclude + getRandom().nextInt(maxExclude - minInclude);
  }

  static List<int> randomInts(int length) {
    final list = List.generate(length, (i) => i);
    for (var i = 0; i < length; i++) {
      final randomIndex = randomIntRange(i, length);
      final temp = list[i];
      list[i] = list[randomIndex];
      list[randomIndex] = temp;
    }
    return list;
  }

  static double randomDouble() => getRandom().nextDouble();

  static double randomDoubleRange(double minInclude, double maxExclude) {
    return minInclude + (maxExclude - minInclude) * getRandom().nextDouble();
  }

  static Decimal randomDecimal() => Decimal.parse(getRandom().nextDouble().toString());

  static T randomEle<T>(List<T> list, [int? limit]) {
    limit ??= list.length;
    if (limit > list.length) limit = list.length;
    return list[randomInt(limit)];
  }

  static List<T> randomEles<T>(List<T> list, int count) {
    final result = <T>[];
    final limit = list.length;
    while (result.length < count) {
      result.add(randomEle(list, limit));
    }
    return result;
  }

  static List<T> randomEleList<T>(List<T> source, int count) {
    if (count >= source.length) return List.of(source);
    return randomInts(source.length)
        .sublist(0, count)
        .map((e) => source[e])
        .toList();
  }

  static Set<T> randomEleSet<T>(Iterable<T> collection, int count) {
    final source = collection.toSet().toList();
    if (count > source.length) throw ArgumentError("Count exceeds collection size");
    final result = <T>{};
    final limit = source.length;
    while (result.length < count) {
      result.add(randomEle(source, limit));
    }
    return result;
  }

  static String randomString(int length) => _randomString(baseCharNumber, length);

  static String randomNumbers(int length) => _randomString(baseNumber, length);

  static String _randomString(String base, int length) {
    if (base.isEmpty) return "";
    final sb = StringBuffer();
    for (var i = 0; i < length; i++) {
      sb.write(base[randomInt(base.length)]);
    }
    return sb.toString();
  }

  static String randomChinese(int length) {
    return String.fromCharCodes(
      List.generate(length, (_) => randomIntRange(0x4e00, 0x9fff + 1)),
    );
  }

  static DateTime randomDay(DateTime baseDate, int minDays, int maxDays) {
    return baseDate.add(Duration(days: randomIntRange(minDays, maxDays)));
  }

}

class WeightRandom<T> {
  final List<WeightObj<T>> _weights;
  late List<double> _cumulativeWeights;

  WeightRandom(Iterable<WeightObj<T>> weights) : _weights = List.of(weights) {
    var total = _weights.fold(0.0, (sum, w) => sum + w.weight);
    _cumulativeWeights = _weights
        .map((w) => w.weight / total)
        .fold<List<double>>(
      [],
          (list, w) => list..add((list.isEmpty ? 0 : list.last) + w),
    );
  }

  T next() {
    final random = RandomUtil.randomDouble();
    final index = _cumulativeWeights.indexWhere((w) => w >= random);
    return _weights[index >= 0 ? index : _weights.length - 1].value;
  }
}

class WeightObj<T> {
  final double weight;
  final T value;

  WeightObj(this.weight, this.value);
}